/*
 * realtime_timer.c
 *
 * Copyright 2011 Pieter Agten
 *
 * This file is part of Yarf.
 *
 * Yarf is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Yarf is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Yarf.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @file   realtime_timer.c
 * @author Pieter Agten (pieter.agten@gmail.com)
 * @date   9 okt 2011
 * @brief  The real-time timer is a high resolution, high priority timer which
 *         is used for controlling the rate at which the stepper motors are
 *         stepped.
 *
 * This component uses hardware timer 2 with a /8 prescaler. Timer 2 has the
 * highest interrupt priority of all timers, which guarantees this timer's
 * interrupts will be executed very shortly after they are raised. The /8
 * prescaler provides a resolution of 0,4 us and a maximum interval of 102 us.
 * Time intervals beyond this maximum hardware interval are implemented in
 * software by repeatedly setting the timer with the maximum hardware interval
 * until the remaining interval is within range.
 */

#include "realtime_timer.h"

#include "hardware/fastio.h"
#include "yarf.h"
#include "util/math.h"

#include <stdint.h>
#include <stdlib.h>
#include <avr/interrupt.h>
#include <util/atomic.h>

/**
 * The number of ticks the real-time timer takes in one microsecond.
 */
#define RTTIMER_TICKS_PER_US (((float)F_CPU)/8/1000000)

/**
 * A pointer to a \a realtime_task_t structure indicating the task to be
 * executed when the timer rings.
 */
static volatile realtime_task_t *pending_task;


/**
 * Sets the hardware timer 2 to raise an interrupt after a specified number of
 * timer ticks.
 *
 * @param ticks the number of timer ticks after which the interrupt should be
 *        raised.
 * @note The hardware timer should be disabled when calling this function.
 * 
 * If \a ticks is greater than the maximum possible hardware time interval, an
 * interrupt will be raised after the maximum possible hardware time interval.
 */
static inline void
timer2_set(long ticks)
{
  /* Note: the timer should be disabled at this point */

  /* Limit the number of ticks to the maximum possible ticks in hardware */
  uint8_t hw_ticks = MIN(UINT8_MAX, MAX(0,ticks));

  /* Set TCNTn to 0 */
  TCNT2 = 0;
  /* Set the TOP register to the number of ticks we want to wait */
  OCR2A = hw_ticks;
  
  /* Enable timer 2 (with /8 prescaler) */
  TCCR2B =  (2 << CS20);
}

/**
 * Interrupt service routine for the hardware timer.
 *
 * This routine executes the scheduled task if the specified number of ticks
 * have passed, or reschedules the timer if more ticks must elapse before the
 * task must be executed.
 */
ISR(TIMER2_COMPA_vect, ISR_BLOCK)
{
  /* Disable the timer */
  TCCR2B = 0;

  if (pending_task != NULL) {
    if (pending_task->ticks_remaining > OCR2A) {
      pending_task->ticks_remaining -= OCR2A;

      /* The delay has not been passed yet */
      timer2_set(pending_task->ticks_remaining);
    } else {
      /* The delay has been passed, let's execute the task */

      volatile realtime_task_t *t = pending_task;
      /* Set pending_task to NULL so f will be able to schedule another task */
      pending_task = NULL;
      t->f();
    }
  }
}


void
rttimer_init(void)
{
  pending_task = NULL;
  
  /* Select OCR2A CTC mode; */
  TCCR2A = (2 << WGM20);
  TCCR2B = 0;

  /* Clear all timer 2 interrupt flags */
  TIFR2 = _BV(OCF2B)|_BV(OCF2A)|_BV(TOV2);

  /* Enable the OCR2A interrupt */
  TIMSK2 = _BV(OCIE2A);
}


int
rttimer_schedule_ticks(realtime_task_t *t, uint16_t ticks)
{
  if (pending_task != NULL) {
    return RTTIMER_BUSY;
  }
  pending_task = t;

  pending_task->ticks_remaining = ticks;
  timer2_set(pending_task->ticks_remaining);
  
  return RTTIMER_SUCCESS;
}


int
rttimer_cancel(void)
{
  if (pending_task == NULL) {
    return RTTIMER_NOT_RUNNING;
  }

  /* Disable the timer */
  TCCR2B = 0;
  
  /* Clear all timer 2 interrupt flags */
  TIFR2 = _BV(OCF2B)|_BV(OCF2A)|_BV(TOV2);

  /* Remove the pending task */
  pending_task = NULL;

  return RTTIMER_SUCCESS;
}

